﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using game.LoadScene;

namespace game
{
    public enum Dir
    {
        NONE,
        UP,
        DOWN,
        LEFT,
        RIGHT
    }

    public enum GameState
    {
        Init,
        Ready,
        Swap,
        Fall,
    }

    public class Panel : SceneConstructor
    {
        Transform cellRoot;
        Transform elementRoot;
        Transform effectRoot;

        List<CellPrefab> cellList;
        List<CellPrefab> dirtyCells = new List<CellPrefab>();
        List<CellPrefab> markCells = new List<CellPrefab>();

        GameState state = GameState.Init;


        protected override IEnumerator DoConstruction()
        {       
            CreateRoot();
            yield return null;
            CreateCells();
            yield return null;
            CreateElement();
            yield return null;
            state = GameState.Ready;
        }
        void CreateRoot()
        {
            cellRoot = new GameObject("[CellRoot]").transform;
            cellRoot.SetParent(transform);
            elementRoot = new GameObject("[ElementRoot]").transform;
            elementRoot.SetParent(transform);
            effectRoot = new GameObject("[EffectRoot]").transform;
            effectRoot.SetParent(transform);
        }
        void CreateCells()
        {
            int _colCount = LevelDatabase.Instance.colCount;
            int _rowCount = LevelDatabase.Instance.rowCount;
            Vector2 _offset = new Vector2(CellDatabase.Instance.cellSize / 2 - _colCount / 2f * CellDatabase.Instance.cellSize,
                CellDatabase.Instance.cellSize / 2 - _rowCount / 2f * CellDatabase.Instance.cellSize);
            if (cellList == null)
            {
                cellList = new List<CellPrefab>();
            }
            cellList.Clear();
            for (int y = 0, yMax = _rowCount; y < yMax; y++) 
            {
                for (int x = 0, xMax = _colCount; x < xMax; x++) 
                {
                    var _newCell = Instantiate(CellDatabase.Instance.cellPrefab, cellRoot);
                    _newCell.OnCellCreate(x, y, CellDatabase.Instance.XY2Pos(x, y, _offset));
                    _newCell.RegisterEvent(OnCellDragCallback);
                    cellList.Add(_newCell);
                }
            }
        }
        void CreateElement()
        {
            List<int> _ignore = new List<int>();
            CellPrefab _tmp1, _tmp2;
            foreach (var _cell in cellList)
            {          
                _tmp1 = GetCellByDir(_cell, Dir.LEFT);
                if (_tmp1)
                {
                    _tmp2 = GetCellByDir(_tmp1, Dir.LEFT);
                    if (_tmp2 && _tmp1.bandingElement.Type == _tmp2.bandingElement.Type)
                    {
                        _ignore.Add(_tmp1.bandingElement.Type);
                    }
                }
                _tmp1 = GetCellByDir(_cell, Dir.DOWN);
                if (_tmp1)
                {
                    _tmp2 = GetCellByDir(_tmp1, Dir.DOWN);
                    if (_tmp2 && _tmp1.bandingElement.Type == _tmp2.bandingElement.Type)
                    {
                        _ignore.Add(_tmp1.bandingElement.Type);
                    } 
                }

                var _newEle = ElementDatabase.Instance.RandomElement(_ignore.ToArray());
                _newEle.transform.SetParent(elementRoot);
                _newEle.OnElementCreate(_cell.X, _cell.Y);
                _cell.bindElement(_newEle);
                _ignore.Clear();
            }
        }

        CellPrefab GetCellByDir(CellPrefab _cell, Dir _dir)
        {
            if (_cell == null) return null;
            int _x = _cell.X;
            int _y = _cell.Y;
            switch (_dir)
            {
                case Dir.UP:
                    _y += 1;
                    break;
                case Dir.DOWN:
                    _y -= 1;
                    break;
                case Dir.LEFT:
                    _x -= 1;
                    break;
                case Dir.RIGHT:
                    _x += 1;
                    break;
            }
            if (_x < 0 || _y < 0 || _x >= LevelDatabase.Instance.colCount || _y >= LevelDatabase.Instance.rowCount) return null;
            return cellList[_y * LevelDatabase.Instance.colCount + _x];
        }

        void GetCheckCellsV(CellPrefab _cell, out List<CellPrefab> ret, bool ignoreSame = false)
        {
            List<CellPrefab> _ret = new List<CellPrefab>() { _cell };
            int _targetType = _cell.bandingElement.Type;
            CellPrefab _tmp = _cell;
            while (true)
            {
                _tmp =  GetCellByDir(_tmp, Dir.UP);
                if (_tmp && !_tmp.IsEmpty && (_tmp.bandingElement.Type == _targetType || ignoreSame))
                {
                    _ret.Add(_tmp);
                }
                else
                {
                    break;
                }
            }
            _tmp = _cell;
            while (true)
            {
                _tmp = GetCellByDir(_tmp, Dir.DOWN);
                if (_tmp && !_tmp.IsEmpty && (_tmp.bandingElement.Type == _targetType || ignoreSame))
                {
                    _ret.Add(_tmp);
                }
                else
                {
                    break;
                }
            }
            ret = _ret;
        }

        void GetCheckCellsH(CellPrefab _cell, out List<CellPrefab> ret, bool ignoreSame = false)
        {
            List<CellPrefab> _ret = new List<CellPrefab>() { _cell };
            int _targetType = _cell.bandingElement.Type;
            CellPrefab _tmp = _cell;
            while (true)
            {
                _tmp = GetCellByDir(_tmp, Dir.LEFT);
                if (_tmp && !_tmp.IsEmpty && (_tmp.bandingElement.Type == _targetType || ignoreSame))
                {
                    _ret.Add(_tmp);
                }
                else
                {
                    break;
                }
            }
            _tmp = _cell;
            while (true)
            {
                _tmp = GetCellByDir(_tmp, Dir.RIGHT);
                if (_tmp && !_tmp.IsEmpty && (_tmp.bandingElement.Type == _targetType || ignoreSame))
                {
                    _ret.Add(_tmp);
                }
                else
                {
                    break;
                }
            }
            ret = _ret;
        }

        void GetCheckCellsB(CellPrefab _cell, out List<CellPrefab> ret)
        {
            List<CellPrefab> _ret = new List<CellPrefab>() { _cell };
            CellPrefab _tmp = _cell;
            _tmp = GetCellByDir(_tmp, Dir.RIGHT);
            if (_tmp != null)
            {
                _ret.Add(_tmp);
                _tmp = GetCellByDir(_tmp, Dir.DOWN);
                if (_tmp != null)
                {
                    _ret.Add(_tmp);
                }
            }
            _tmp = _cell;
            _tmp = GetCellByDir(_tmp, Dir.DOWN);
            if (_tmp != null)
            {
                _ret.Add(_tmp);
                _tmp = GetCellByDir(_tmp, Dir.LEFT);
                if (_tmp != null)
                {
                    _ret.Add(_tmp);
                }
            }
            _tmp = _cell;
            _tmp = GetCellByDir(_tmp, Dir.LEFT);
            if (_tmp != null)
            {
                _ret.Add(_tmp);
                _tmp = GetCellByDir(_tmp, Dir.UP);
                if (_tmp != null)
                {
                    _ret.Add(_tmp);
                }
            }
            _tmp = _cell;
            _tmp = GetCellByDir(_tmp, Dir.UP);
            if (_tmp != null)
            {
                _ret.Add(_tmp);
                _tmp = GetCellByDir(_tmp, Dir.RIGHT);
                if (_tmp != null)
                {
                    _ret.Add(_tmp);
                }
            }
            ret = _ret;
        }

        void GetCheckCellsAll(CellPrefab _cell, out List<CellPrefab> ret)
        {
            List<CellPrefab> _ret = new List<CellPrefab>() { _cell };
            int _targetType = _cell.bandingElement.Type;
            foreach (var tmp in cellList)
            {
                if (tmp.bandingElement.Type == _targetType)
                {
                    _ret.Add(tmp);
                }
            }
            ret = _ret;
        }

        void SetToDirty(CellPrefab _cell)
        {
            if (!dirtyCells.Contains(_cell))
            {
                dirtyCells.Add(_cell);
            }
        }

        void ClearDirtyCells()
        {
            dirtyCells.Clear();
        }

        void SetToMark(CellPrefab _cell)
        {
            if (!markCells.Contains(_cell))
            {
                markCells.Add(_cell);
            }
        }
        void SetToMark(List<CellPrefab> _lis)
        {
            markCells.AddRange(_lis);
        }

        void ClearMark(CellPrefab _cell)
        {
            if (markCells.Contains(_cell))
            {
                markCells.Remove(_cell);
            }
        }

        void ClearDirtyMark()
        {
            markCells.Clear();
        }
        CellPrefab endDragCell;
        CellPrefab endDragFindCell;

        void CanCellDragSwap()
        {
            if (endDragCell && endDragFindCell)
            {
                endDragFindCell.SwapCellElement(endDragCell);
                endDragCell = null;
                endDragFindCell = null;
            }
        }

        void OnCellDragCallback(CellPrefab _cell, Vector2 _dirty)
        {
            if (state != GameState.Ready) return;

            float xCos = Vector2.Dot(_dirty.normalized, Vector2.right);
            float yCos = Vector2.Dot(_dirty.normalized, Vector2.up);           

            CellPrefab _findCell = null;

            if (Mathf.Abs(xCos) > Mathf.Abs(yCos))
            {
                if (xCos > 0)
                {
                    _findCell = GetCellByDir(_cell, Dir.RIGHT);
                }
                else
                {
                    _findCell = GetCellByDir(_cell, Dir.LEFT);
                }
            }
            else
            {
                if (yCos > 0)
                {
                    _findCell = GetCellByDir(_cell, Dir.UP);
                }
                else
                {
                    _findCell = GetCellByDir(_cell, Dir.DOWN);
                }
            }
            if (_findCell)
            {
                state = GameState.Swap;

                _cell.SwapCellElement(_findCell);
                endDragCell = _cell;
                endDragFindCell = _findCell;

                SetToDirty(endDragCell);
                SetToDirty(endDragFindCell);

                StartCoroutine(MatchStep());
            }
        }

        IEnumerator MatchStep()
        {
            while (dirtyCells.Count > 0)
            {
                //等待所有dirtyCell都idel
                var it = WaitForDirtyCellsToIdel();
                while (it.MoveNext())
                {
                    yield return null;
                }
                //检测是不是要消除的元素
                ClearDirtyMark();

                //同色炸弹检测
                bool isHaveFree = false;
                int _type = 0;
                if (endDragCell != null && endDragFindCell != null)
                {
                    if(endDragCell.bandingElement.State != ElementState.Free)
                    {
                        _type = endDragCell.bandingElement.Type;
                    }
                    else if (endDragFindCell.bandingElement.State != ElementState.Free)
                    {
                        _type = endDragFindCell.bandingElement.Type;
                    }
                    if (endDragCell.bandingElement.State == ElementState.Free)
                    {
                        endDragCell.bandingElement.Type = _type;
                        SetToMark(endDragCell);
                        isHaveFree = true;
                    }
                    else if (endDragFindCell.bandingElement.State == ElementState.Free)
                    {
                        endDragFindCell.bandingElement.Type = _type;
                        SetToMark(endDragFindCell);
                        isHaveFree = true;
                    }
                }
                if(!isHaveFree)
                {
                    DoMatch();
                }

                if (markCells.Count > 0)
                {
                    //要消除
                    //消除，然后继续执行后面逻辑
                    endDragCell = null;
                    endDragFindCell = null;
                    ClearDirtyCells();
                }
                else
                {
                    //不消除
                    //换位，然后结束判断流程
                    CanCellDragSwap();

                    it = WaitForDirtyCellsToIdel();
                    while (it.MoveNext())
                    {
                        yield return null;
                    }
                    ClearDirtyCells();
                    //break;
                }
                ClearDirtyCells();
                //将markCells中的元素回收
                RecyckeMarkCell();
                it = WaitForMarkCellsToIdel();
                while (it.MoveNext())
                {
                    yield return null;
                }
                //将回收后空的cell进行上方元素的填充，填充的元素依然是diry状态
                FallElement();
            //go to 步骤1
        }
        state = GameState.Ready;
            yield return null;
        }

        void DoMatch()
        {
            //将确定要消除哪些元素，放入markcells列表中
            foreach (var _cell in dirtyCells)
            {
                int countV;
                int countH;
                List<CellPrefab> _tmpList;
                GetCheckCellsV(_cell, out _tmpList);
                countV = _tmpList.Count;
                if (_tmpList.Count > 2)
                {
                    foreach (var token in _tmpList)
                    {
                        SetToMark(token);
                    }
                }
                GetCheckCellsH(_cell, out _tmpList);
                countH = _tmpList.Count;
                if (_tmpList.Count > 2)
                {
                    foreach (var token in _tmpList)
                    {
                        SetToMark(token);
                    }
                }        
                if (SpeciaMatch.IsSpecialMatch(_cell, countV, countH))
                {
                    ClearMark(_cell);
                }
            }
        }

        void RecyckeMarkCell()
        {
            List<CellPrefab> _tmpMark = new List<CellPrefab>(markCells);
            List<CellPrefab> _tmpNewMark = new List<CellPrefab>();
            while (_tmpMark.Count > 0)
            {
                foreach (var _cell in markCells)
                {
                    if (_cell.bandingElement.State == ElementState.HBomb && !_cell.bandingElement.IsEffected)
                    {
                        _cell.bandingElement.IsEffected = true;
                        List<CellPrefab> _tmpList;
                        GetCheckCellsH(_cell, out _tmpList, true);
                        foreach (var _new in _tmpList)
                        {
                            if (!_tmpNewMark.Contains(_new))
                            {
                                _tmpNewMark.Add(_new);
                            }
                        }
                    }
                    else if (_cell.bandingElement.State == ElementState.VBomb && !_cell.bandingElement.IsEffected)
                    {
                        _cell.bandingElement.IsEffected = true;
                        List<CellPrefab> _tmpList;
                        GetCheckCellsV(_cell, out _tmpList, true);
                        foreach (var _new in _tmpList)
                        {
                            if (!_tmpNewMark.Contains(_new))
                            {
                                _tmpNewMark.Add(_new);
                            }
                        }
                    }
                    else if (_cell.bandingElement.State == ElementState.Bomb && !_cell.bandingElement.IsEffected)
                    {
                        _cell.bandingElement.IsEffected = true;
                        List<CellPrefab> _tmpList;
                        GetCheckCellsB(_cell, out _tmpList);
                        foreach (var _new in _tmpList)
                        {
                            if (!_tmpNewMark.Contains(_new))
                            {
                                _tmpNewMark.Add(_new);
                            }
                        }
                    }
                    else if (_cell.bandingElement.State == ElementState.Free && !_cell.bandingElement.IsEffected)
                    {
                        _cell.bandingElement.IsEffected = true;
                        List<CellPrefab> _tmpList;
                        GetCheckCellsAll(_cell, out _tmpList);
                        foreach (var _new in _tmpList)
                        {
                            if (!_tmpNewMark.Contains(_new))
                            {
                                _tmpNewMark.Add(_new);
                            }
                        }
                    }
                }
                _tmpMark.Clear();
                if (_tmpNewMark.Count > 0)
                {
                    foreach (var token in _tmpNewMark)
                    {
                        SetToMark(token);
                    }
                    _tmpMark.AddRange(_tmpNewMark);
                }
                _tmpNewMark.Clear();
            }
            foreach (var _cell in markCells)
            {
                _cell.Recycle();
            }
        }
        void FallElement()
        {
            List<CellPrefab> _emptyCells = new List<CellPrefab>(markCells);
            List<CellPrefab> _tmpNewEmptyCells = new List<CellPrefab>();
            while (_emptyCells.Count > 0)
            {
                //把_emptyCell排序，保证从左下开始
                _emptyCells.Sort((x, y) => y.IndexInPanel - x.IndexInPanel);
                //把现在的_emptyCell向上检测哪一个是掉落目标
                foreach (var _empty in _emptyCells)
                {
                    CellPrefab _tmp = _empty;
                    while (true)
                    {
                        _tmp = GetCellByDir(_tmp, Dir.UP);
                        if (_tmp != null && !_tmp.IsEmpty)
                        {
                            //1.找到cell不为空的情况，移动Element
                            _empty.FallElement(_tmp);
                            _tmpNewEmptyCells.Add(_tmp);
                            SetToDirty(_empty);
                            break;
                        }
                        else if (_tmp == null)
                        {
                            //2.找不到cell不为空的情况，生成Element并移动
                            Element _newEle = ElementDatabase.Instance.RandomElement();
                            _newEle.transform.SetParent(elementRoot);
                            _empty.FallElement(_newEle);
                            _newEle.transform.position = _empty.transform.position + new Vector3(0, CellDatabase.Instance.cellSize * 5 , 0);
                            SetToDirty(_empty);
                            break;
                        }
                    }
                }
                //掉落后，生成新的_emptyCell
                _emptyCells.Clear();
                if (_tmpNewEmptyCells.Count > 0)
                {
                    _emptyCells.AddRange(_tmpNewEmptyCells);
                }
                _tmpNewEmptyCells.Clear();
            }
        }

        IEnumerator WaitForDirtyCellsToIdel()
        {
            while (true)
            {
                bool isAllIdel = true;                      
                foreach (var _cell in dirtyCells)
                {
                    if (!_cell.isIdel)
                    {
                        isAllIdel = false;              
                        break;
                    }
                }
                if (isAllIdel)
                    break;

                yield return null;
            }
        }
        IEnumerator WaitForMarkCellsToIdel()
        {
            while (true)
            {
                bool isAllIdel = true;
                foreach (var _cell in markCells)
                {
                    if (!_cell.isIdel)
                    {
                        isAllIdel = false;
                        break;
                    }
                }
                if (isAllIdel)
                    break;

                yield return null;
            }
        }
    }
}

